/* Complet (Autocompletion through Graph Databases)
 *  Copyright (C) 2012  Paul Wagner, Till Speicher
 *
 *  This program is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation; either version 3 of the License, or (at your
 *  option) any later version.
 *
 *  This program is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 *  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 *  for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, see <http:/* www.gnu.org/licenses/>.
 */

/*
 * Class for performing lookups using n-grams and language models.
 * Give sentence using setSentence()
 * Make lookup using eval()
 * See result using getResult() or toString()
 *
 * @ author Paul Wagner, René Pickhardt
 */
package complet.lm;

import complet.helper.ConfigHelper;
import complet.helper.IOHelper;
import java.util.Map.Entry;
import java.io.BufferedReader;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import javolution.util.FastMap;

public class Retrieval {

    // PROPERTIES
    private String words;
    private String offset;
    private int n;
    private FastMap<String, Integer> ngrams;
    private HashMap<String, Integer> n_1grams;
    private long not_used = 0;
    private TreeMap<String, Double> res;
    private TreeMap<String, Double> res_sorted;
    private boolean FULL_LIST = false;
    private final Comparator<String> COMP_REL = new Comparator<String>() {

        public int compare(String s1, String s2) {
            //return res.get(s2).getAVG_RELLOC().compareTo(res.get(s1).getAVG_RELLOC());
            if (res.get(s1) > res.get(s2)) {
                return -1;
            }
            if (res.get(s1) < res.get(s2)) {
                return 1;
            }
            int _return = s1.compareTo(s2);
            if (_return != 0) {
                return _return;
            }
            return 1;
        }
    };

    /*
     * Constructor using ConfigHelper
     */
    public Retrieval(int n) {
        this(ConfigHelper.IN_PATH, n);
    }

    /*
     * Constructor using manual config values
     *
     */
    public Retrieval(String inputfile, int n) {
        this.ngrams = new FastMap<String, Integer>();
        this.n_1grams = new HashMap<String, Integer>();
        this.res = new TreeMap<String, Double>();
        this.res_sorted = new TreeMap<String, Double>(COMP_REL);
        this.n = n;
        bufferFile(inputfile);
    }

    // GETTER AND SETTER
    public int getN() {
        return n;
    }

    public void setN(int n) {
        this.n = n;
    }

    public long getUnused() {
        return this.not_used;
    }

    public long getUsed() {
        return this.ngrams.size();
    }

    // METHODS

    /*
     * Set sentence before performing lookup
     * Please be aware of the fact, that your sentence shouldn't be longer than your DB provides!
     *
     * @param words Words in the sentence
     * @param offest Letters known from the current word
     */
    public void setSentence(String words, String offset) {
        this.words = words.trim();
        if (this.words.split(" ").length + 1 != n) {
            this.words = null;
            IOHelper.log("(lm.Retrieval) WARNING: setSentence() Wordlength out of bounds, no result!");
        }
        this.offset = offset;
    }

    /*
     * Perfoms lookup
     *
     * @return lookuptime Evaluating time in ms
     */
    public double eval() {
        long start_time = System.nanoTime();
        this.res.clear();
        this.res_sorted.clear();

        if (words.split(" ").length + 1 != n) {
            return -1d;
        }

        try {
            // Get count of all n-1 grams matching our wordlist
            Integer tmp = n_1grams.get(words);
            double cnt_all = 1d;
            if (tmp != null) {
                cnt_all = tmp;
            }
            int size = n_1grams.size();

            // Now get counts of all possible precedings and put result to resultset
            // Words: ich gehe ueber die
            // key: ich gehe über die strasse
            // if(key.startsWith(words))
//            for (Entry val : ngrams.entrySet()) {
//                String tmfp = (String) val.getKey();
//                String key = (String) val.getKey();
//                String last = key.substring(key.lastIndexOf(" ") + 1);
//                String first = key.substring(0, key.lastIndexOf(" "));
//                // First words have to match wordlist, last one offset
//                if (first.equals(words)) {
//                    if (last.startsWith(offset)) {
//                        double qual = ((Integer) val.getValue() + 1) / (cnt_all + size);
//                        res.put(last, qual);
//                    }
//                }
//            }
            String key, last;
            for (FastMap.Entry<String, Integer> e = ngrams.head(), end = ngrams.tail(); (e = e.getNext()) != end;) {
                key = e.getKey();
                if (key.startsWith(words)) {
                    int k = key.lastIndexOf(" ");
                    last = key.substring(k + 1);
                    if (last.startsWith(offset)) {
                        double qual = (e.getValue() + 1) / (cnt_all + size);
                        res.put(last, qual);
                    }
                }
            }
            res_sorted.putAll(res);
        } catch (Exception e) {
            IOHelper.logError("(lm.Retrieval.eval()) " + e.getMessage());
            return -1;
        }
        long end_time = System.nanoTime();
        return (double) (Math.round((double) (end_time - start_time) / 1000)) / 1000;
    }

    /*
     * Returns unordered resultset
     *
     */
    public TreeMap<String, Double> getResult() {
        return this.res;
    }

    public Set<String> getSortedKeySet() {
        return this.res_sorted.keySet();
    }

    /*
     * Load ngrams file and store ngrams in hashmap
     * Also aggregate n-1 grams, we will need them for lookup
     */
    private void bufferFile(String filename) {
        BufferedReader in = IOHelper.openReadFile(filename);
        ngrams.clear();
        n_1grams.clear();
        try {
            String strLine = "";
            int oc = 0;
            while (true && ((strLine = in.readLine()) != null)) {
                //oc++;
                if(oc > 6000000){
                    break;
                }
                String ngm = strLine.split("\t")[0].trim();
                int cnt = Integer.parseInt(strLine.split("\t")[1]);
                if (ngm.split(" ").length == n) {
                    ngm = ngm.replaceAll("ß", "ss");
                    ngm = ngm.replaceAll("ü", "ue");
                    ngm = ngm.replaceAll("ä", "ae");
                    ngm = ngm.replaceAll("ö", "oe");
                    ngm = ngm.replaceAll("Ü", "ue");
                    ngm = ngm.replaceAll("Ä", "ae");
                    ngm = ngm.replaceAll("Ö", "oe");
                    ngm = ngm.toLowerCase();
                    ngm = ngm.replaceAll("[^a-z ]", "");

                    int cnt_n = 0;
                    if (ngrams.containsKey(ngm)) {
                        cnt_n = ngrams.get(ngm);
                    }
                    ngrams.put(ngm, cnt + cnt_n);

                    String n_1gm = ngm.replaceFirst("\\s+\\w+$", "");
                    int cnt_n1 = 0;
                    if (n_1grams.containsKey(n_1gm)) {
                        cnt_n1 = n_1grams.get(n_1gm);
                    }
                    n_1grams.put(n_1gm, cnt + cnt_n1);
                } else {
                    if (ngm.split(" ").length > 1) {
                        not_used++;
                    }
                }
            }
        } catch (Exception e) {
            IOHelper.logError("(lm.Retrieval) Error reading ngrams: " + e.getMessage());
        }
    }
}
